package com.xyazm.order.statemachine.action.order.cancel;

import com.xyazm.common.constants.RedisLockKeyConstants;
import com.xyazm.common.enums.AfterSaleTypeEnum;
import com.xyazm.common.enums.OrderStatusChangeEnum;
import com.xyazm.common.enums.OrderStatusEnum;
import com.xyazm.common.redis.RedisLock;
import com.xyazm.common.utils.ParamCheckUtil;
import com.xyazm.fulfill.domain.param.CancelFulfillParam;
import com.xyazm.order.dao.OrderInfoDAO;
import com.xyazm.order.dao.OrderItemDAO;
import com.xyazm.order.domain.dto.CancelOrderAssembleDTO;
import com.xyazm.order.domain.dto.OrderInfoDTO;
import com.xyazm.order.domain.dto.OrderItemDTO;
import com.xyazm.order.domain.entity.OrderInfoDO;
import com.xyazm.order.domain.entity.OrderItemDO;
import com.xyazm.order.domain.param.CancelOrderParam;
import com.xyazm.order.enums.ExecuteFulfillMarkEnum;
import com.xyazm.order.enums.OrderCancelTypeEnum;
import com.xyazm.order.exception.OrderBizException;
import com.xyazm.order.exception.OrderErrorCodeEnum;
import com.xyazm.order.mapstruct.OrderApiMapstruct;
import com.xyazm.order.remote.FulfillRemote;
import com.xyazm.order.service.IMqMessageService;
import com.xyazm.order.statemachine.OrderStateMachine;
import com.xyazm.order.statemachine.StateMachineFactory;
import com.xyazm.order.statemachine.action.OrderStateAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.List;

/**
 * 订单取消 action
 * @author xuyou
 * @date 2023年02月23日 22:31
 */
@Slf4j
@Component
public class OrderCancelAction extends OrderStateAction<CancelOrderParam> {

    @Autowired
    private RedisLock redisLock;
    @Autowired
    private OrderInfoDAO orderInfoDAO;
    @Autowired
    private OrderItemDAO orderItemDAO;
    @Autowired
    private IMqMessageService mqMessageService;
    @Resource
    private StateMachineFactory stateMachineFactory;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private FulfillRemote fulfillRemote;

    @Override
    public OrderStatusChangeEnum event() {
        return OrderStatusChangeEnum.ORDER_CANCEL;
    }


    /**
     * 取消订单只允许整笔取消,包含2套场景：
     * 场景1、超时未支付取消：操作的order是父单,入口是延迟MQ和XXL-JOB定时任务 (操作的数据类型见：下单后)
     * 场景2、用户手动取消: 操作的order是任意1笔(普通订单 or 预售订单 or 虚拟订单),入口是AfterSaleController#cancelOrder方法 (操作的数据类型见：支付后)
     * <p>
     * 拆单依据:
     * 一笔订单购买不同类型的商品,生单时会拆单,反之不拆
     * <p>
     * 正向生单拆单后,订单数据的状态变化：
     * (下单后)                   (支付后 状态>= 20)         (自动取消订单后)
     * +─────+─────+             +─────+─────+             +─────+─────+
     * | 类型 | 状态|             | 类型 | 状态|             | 类型 | 状态 |
     * +─────+─────+             +─────+─────+             +─────+─────+
     * | 父单 | 10  |            | 父单 | 127 |             | 父单 | 70  |
     * | 普通 | 127 |    =>      | 普通 | 20  |     =>      | 普通 | 70  |
     * | 预售 | 127 |            | 预售 | 20  |             | 预售 | 70  |
     * | 虚拟 | 127 |            | 虚拟 | 20  |             | 虚拟 | 70  |
     * +─────+─────+             +─────+─────+             +─────+─────+
     * <p>
     * 注:普通/预售/虚拟 不同订单和订单条目的关系都是 1(订单) v 多(条目)
     */
    @Override
    protected OrderInfoDTO onStateChangeInternal(OrderStatusChangeEnum event, CancelOrderParam context) {
        // 入参检查
        checkCancelOrderParam(context);
        // 分布式锁 - 取消支付
        String key = RedisLockKeyConstants.CANCEL_KEY + context.getOrderId();
        boolean lock = redisLock.tryLock(key);
        if (!lock) {
            throw new OrderBizException(OrderErrorCodeEnum.CANCEL_ORDER_REPEAT);
        }
        try {
            // 组装取消订单数据
            CancelOrderAssembleDTO cancelOrderAssembleDTO = buildAssembleDTO(context);
            // 检查订单状态
            checkCancelOrderStatus(cancelOrderAssembleDTO);
            // 设置调用履约接口标记
            setFulfillMark(cancelOrderAssembleDTO);
            // 执行履约取消、更新订单状态、新增订单日志操作
            cancelOrderFulfillmentAndUpdateOrderStatus(cancelOrderAssembleDTO);
            // 向下游发送释放权益资产MQ
            sendReleaseAssetsMq(cancelOrderAssembleDTO);
        } catch (Exception e) {
            throw new OrderBizException(e.getMessage());
        } finally {
            redisLock.unlock(key);
        }
        // 返回null 不发送订单变更事件
        return null;
    }

    /**
     * 发送释放权益资产MQ
     * @author xuyou
     * 2023-02-24 20:40
     * @param cancelOrderAssembleDTO
    */
    private void sendReleaseAssetsMq(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        mqMessageService.sendReleaseAssetsMessage(cancelOrderAssembleDTO);
    }



    /**
     * 设置调用履约接口标记
     * @author xuyou
     * 2023-02-24 20:34
     * @param cancelOrderAssembleDTO
     */
    private void setFulfillMark(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        //  订单信息
        OrderInfoDTO orderInfoDTO = cancelOrderAssembleDTO.getOrderInfoDTO();
        //  所有类型的订单 在"已创建"状态 不调用履约
        if (OrderStatusEnum.CREATED.getCode().equals(orderInfoDTO.getOrderStatus())) {
            cancelOrderAssembleDTO.setExecuteFulfillMark(ExecuteFulfillMarkEnum.CANNOT_EXECUTE_FULFILL.getCode());
        } else {
            cancelOrderAssembleDTO.setExecuteFulfillMark(ExecuteFulfillMarkEnum.EXECUTE_FULFILL.getCode());
        }
    }


    /**
     * 执行履约取消、更新订单状态、新增订单日志操作
     * @author xuyou
     * 2023-02-24 20:38
     * @param cancelOrderAssembleDTO
    */
    private void cancelOrderFulfillmentAndUpdateOrderStatus(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        //  @Transactional无法生效，需要用编程式事务
        transactionTemplate.execute(transactionStatus -> {
            if (ExecuteFulfillMarkEnum.EXECUTE_FULFILL.getCode().equals(cancelOrderAssembleDTO.getExecuteFulfillMark())) {
                // 取消订单调用履约接口
                cancelFulfill(cancelOrderAssembleDTO);
            }
            // 执行具体场景的取消逻辑
            doSpecialOrderCancel(cancelOrderAssembleDTO);
            return true;
        });
    }

    /**
     * 执行具体场景的取消逻辑
     * @author xuyou
     * 2023-02-24 21:07
     * @param cancelOrderAssembleDTO
    */
    private void doSpecialOrderCancel(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        OrderCancelTypeEnum cancelType = OrderCancelTypeEnum.getByCode(cancelOrderAssembleDTO.getCancelType());
        OrderStatusEnum orderStatus = OrderStatusEnum.getByCode(cancelOrderAssembleDTO.getOrderInfoDTO().getOrderStatus());
        // 如果超时未支付自动取消
        if (OrderCancelTypeEnum.TIMEOUT_CANCELED.equals(cancelType)) {
            // 状态机流转 - 订单自动超时未支付订单取消
            OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(OrderStatusEnum.CREATED);
            orderStateMachine.fire(OrderStatusChangeEnum.ORDER_UN_PAID_AUTO_TIMEOUT_CANCELLED, cancelOrderAssembleDTO);
            return;
        }

        // 如果是用户主动取消订单,并且订单状态是 创建状态
        if (OrderCancelTypeEnum.USER_CANCELED.equals(cancelType) && OrderStatusEnum.CREATED.equals(orderStatus)) {
            OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(OrderStatusEnum.CREATED);
            // 状态机流转 - 订单未支付手动取消
            orderStateMachine.fire(OrderStatusChangeEnum.ORDER_UN_PAID_MANUAL_CANCELLED, cancelOrderAssembleDTO);
            return;
        }
        // 如果是用户主动取消订单,并且订单状态是 已支付状态
        if (OrderCancelTypeEnum.USER_CANCELED.equals(cancelType) && OrderStatusEnum.PAID.equals(orderStatus)) {
            OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(OrderStatusEnum.CREATED);
            // 状态机流转 - 订单已支付手动取消
            orderStateMachine.fire(OrderStatusChangeEnum.ORDER_PAID_MANUAL_CANCELLED, cancelOrderAssembleDTO);
            return;
        }
        // 如果是用户主动取消订单,并且订单状态是 已履约状态
        if (OrderCancelTypeEnum.USER_CANCELED.equals(cancelType) && OrderStatusEnum.FULFILL.equals(orderStatus)) {
            // 状态机流转
            OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(OrderStatusEnum.FULFILL);
            orderStateMachine.fire(OrderStatusChangeEnum.ORDER_FULFILLED_MANUAL_CANCELLED, cancelOrderAssembleDTO);
        }
    }

    /**
     * 取消履约
     * @author xuyou
     * 2022/7/24 0024 13:08
     */
    private void cancelFulfill(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        OrderInfoDTO orderInfoDTO = cancelOrderAssembleDTO.getOrderInfoDTO();
        if (OrderStatusEnum.CREATED.getCode().equals(orderInfoDTO.getOrderStatus())) {
            log.warn("订单状态为已创建,不需取消履约");
            return;
        }
        CancelFulfillParam cancelFulfillParam = OrderApiMapstruct.INSTANCE.convertCancelFulfillParam(orderInfoDTO);
        // 调用履约服务api-履约通知停止配送
        fulfillRemote.cancelFulfill(cancelFulfillParam);
    }




    /**
     * 检查订单状态
     * @author xuyou
     * 2023-02-24 20:34
     * @param cancelOrderAssembleDTO
    */
    private void checkCancelOrderStatus(CancelOrderAssembleDTO cancelOrderAssembleDTO) {
        OrderInfoDTO orderInfoDTO = cancelOrderAssembleDTO.getOrderInfoDTO();
        //  大于已出库状态的订单不能取消, 注:虚拟订单在支付后会自动更新成"已签收",所以已支付的虚拟订单不会被取消
        if (orderInfoDTO.getOrderStatus() >= OrderStatusEnum.OUT_STOCK.getCode()) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_STATUS_CHANGED);
        }
        //  已取消的订单不能重复取消
        if (orderInfoDTO.getOrderStatus().equals(OrderStatusEnum.CANCELLED.getCode())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_STATUS_CANCELED);
        }
    }

    /**
     * 组装 取消订单 数据
     * @author xuyou
     * 2022/6/6 0006 22:02
     * @param param
     */
    private CancelOrderAssembleDTO buildAssembleDTO(CancelOrderParam param) {
        String orderId = param.getOrderId();
        // 获取订单信息
        OrderInfoDTO orderInfoDTO = getOrderInfoDTO(orderId);
        // 设置订单取消类型 0-手动取消 1-超时未支付
        orderInfoDTO.setCancelType(String.valueOf(param.getCancelType()));

        // 获取订单明细信息
        List<OrderItemDTO> orderItemDTOList = getOrderItemListDTO(orderId);

        CancelOrderAssembleDTO cancelOrderAssembleDTO =
                OrderApiMapstruct.INSTANCE.convertCancelOrderAssembleDTO(param);
        cancelOrderAssembleDTO.setOrderInfoDTO(orderInfoDTO);
        cancelOrderAssembleDTO.setOrderItemDTOList(orderItemDTOList);
        // 售后类型:退款
        cancelOrderAssembleDTO.setAfterSaleType(AfterSaleTypeEnum.RETURN_MONEY.getCode());
        return cancelOrderAssembleDTO;
    }

    /**
     * 获取订单明细信息
     * @author xuyou
     * 2022/6/6 0006 22:26
     * @param orderId
     * @return java.util.List<com.xyazm.order.domain.dto.OrderItemDTO>
     */
    private List<OrderItemDTO> getOrderItemListDTO(String orderId) {
        List<OrderItemDO> orderItemDOList = orderItemDAO.listByOrderId(orderId);
        if (orderItemDOList == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_ITEM_IS_NULL);
        }
        return OrderApiMapstruct.INSTANCE.convertOrderItemDTO(orderItemDOList);
    }

    /**
     * 获取订单信息
     * @author xuyou
     * 2022/6/6 0006 22:26
     * @param orderId
     * @return com.xyazm.order.domain.dto.OrderInfoDTO
     */
    private OrderInfoDTO getOrderInfoDTO(String orderId) {
        OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
        if (orderInfoDO == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_NOT_FOUND);
        }
        return OrderApiMapstruct.INSTANCE.convertOrderInfoDTO(orderInfoDO);
    }

    /**
     * 入参检查
     * @author xuyou
     * 2022/6/6 0006 22:37
     * @param param
     */
    private void checkCancelOrderParam(CancelOrderParam param) {
        ParamCheckUtil.checkObjectNonNull(param);

        //  订单状态
        Integer orderStatus = param.getOrderStatus();
        ParamCheckUtil.checkObjectNonNull(orderStatus, OrderErrorCodeEnum.ORDER_STATUS_IS_NULL);

        if (orderStatus.equals(OrderStatusEnum.CANCELLED.getCode())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_STATUS_CANCELED);
        }

        // 订单已出库,不能取消,只能收到货申请售后
        if (orderStatus >= OrderStatusEnum.OUT_STOCK.getCode()) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_STATUS_CHANGED);
        }

        //  订单ID
        String orderId = param.getOrderId();
        ParamCheckUtil.checkStringNonEmpty(orderId, OrderErrorCodeEnum.CANCEL_ORDER_ID_IS_NULL);

        //  业务线标识
        Integer businessIdentifier = param.getBusinessIdentifier();
        ParamCheckUtil.checkObjectNonNull(businessIdentifier, OrderErrorCodeEnum.BUSINESS_IDENTIFIER_IS_NULL);

        //  订单取消类型
        Integer cancelType = param.getCancelType();
        ParamCheckUtil.checkObjectNonNull(cancelType, OrderErrorCodeEnum.CANCEL_TYPE_IS_NULL);

        //  用户ID
        String userId = param.getUserId();
        ParamCheckUtil.checkStringNonEmpty(userId, OrderErrorCodeEnum.USER_ID_IS_NULL);

        //  订单类型
        Integer orderType = param.getOrderType();
        ParamCheckUtil.checkObjectNonNull(orderType, OrderErrorCodeEnum.ORDER_TYPE_IS_NULL);

    }


}
